/*=====================================================================*
 *                   Copyright (C) 2011 Paul Mineiro                   *
 * All rights reserved.                                                *
 *                                                                     *
 * Redistribution and use in source and binary forms, with             *
 * or without modification, are permitted provided that the            *
 * following conditions are met:                                       *
 *                                                                     *
 *     * Redistributions of source code must retain the                *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer.                                       *
 *                                                                     *
 *     * Redistributions in binary form must reproduce the             *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer in the documentation and/or            *
 *     other materials provided with the distribution.                 *
 *                                                                     *
 *     * Neither the name of Paul Mineiro nor the names                *
 *     of other contributors may be used to endorse or promote         *
 *     products derived from this software without specific            *
 *     prior written permission.                                       *
 *                                                                     *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND              *
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,         *
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES               *
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE             *
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER               *
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,                 *
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES            *
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE           *
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR                *
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF          *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT           *
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY              *
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE             *
 * POSSIBILITY OF SUCH DAMAGE.                                         *
 *                                                                     *
 * Contact: Paul Mineiro <paul@mineiro.com>                            *
 *=====================================================================*/

#include "data.h"
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LABELS 1024

static int
read_positive_integer (char**           bufptr,
                       unsigned int*    valptr,
                       unsigned int     maxval,
                       const char*      vname)
{
  char* endptr;

  *valptr = strtoul (*bufptr, &endptr, 10);

  if (endptr > *bufptr)
    {
      if (isspace (*endptr) || *endptr == '\0')
        {
          *bufptr = endptr;

          if (*valptr >= maxval || *valptr == 0)
            {
              fprintf (stderr, "error reading %s: value %u is outside allowed range [1,%u)\n",
                       vname,
                       *valptr,
                       maxval);

              return -1;
            }

          return 0;
        }

      fprintf (stderr, "error reading %s: bad character %hhx\n",
               vname,
               *endptr);

      return -1;
    }

  return -1;
}

static int
read_nonnegative_integer (char**           bufptr,
                          unsigned int*    valptr,
                          unsigned int     maxval,
                          const char*      vname)
{
  char* endptr;

  *valptr = strtoul (*bufptr, &endptr, 10);

  if (endptr > *bufptr)
    {
      if (isspace (*endptr) || *endptr == '\0')
        {
          *bufptr = endptr;

          if (*valptr >= maxval)
            {
              fprintf (stderr, "error reading %s: value %u at or exceeding maximum value %u\n",
                       vname,
                       *valptr,
                       maxval);

              return -1;
            }

          return 0;
        }

      fprintf (stderr, "error reading %s: bad character %hhx\n",
               vname,
               *endptr);

      return -1;
    }

  return -1;
}

static int
read_unit_interval_float (char**           bufptr,
                          float*           valptr)
{
  char* endptr;

  *valptr = strtod (*bufptr, &endptr);

  if (endptr > *bufptr)
    {
      if (isspace (*endptr) || *endptr == '\0')
        {
          *bufptr = endptr;

          if (*valptr <= 0.0 || *valptr >= 1.0)
            {
              fprintf (stderr, 
                       "read_header: error: prior value not in (0, 1): %g\n",
                       *valptr);

              return -1;
            }

          return 0;
        }

      fprintf (stderr, "read_header: error reading prior: bad character %hhx\n",
               *endptr);

      return -1;
    }

  return -1;
}

typedef struct _Header Header;
struct _Header 
{
  unsigned int  n_ratings;
  unsigned int  n_workers;
  unsigned int  n_images;
  unsigned int  n_labels;
  float         priorz[MAX_LABELS];
};

static int
read_header (FILE*     fp,
             Header*   header)
{
  char buf[65536];
  char* bufp = buf;
  float* priorzptr;

  if (fgets (buf, sizeof (buf), fp) == NULL)
    {
      fprintf (stderr, "error: read_header: unexpected EOF\n");
      return -1;
    }

  if (buf[strlen (buf) - 1] != '\n')
    {
      fprintf (stderr, "error: read_header: header line too long\n");
      return errno = ENOMEM, -1;
    }

  /* <numRatings> <numWorkers> <numImages> <prior P(Z=1)> ... <prior p(Z=k)> */

  if (read_positive_integer (&bufp, &header->n_ratings, 1<<31, "numRatings") ||
      read_positive_integer (&bufp, &header->n_workers, 1<<31, "numWorkers") ||
      read_positive_integer (&bufp, &header->n_images, 1<<31, "numImages"))
    {
      fprintf (stderr, "error: read_header: bad header data\n");
      return errno = EINVAL, -1;
    }

  for (header->n_labels = 1, priorzptr = header->priorz + 1; 
       ! read_unit_interval_float (&bufp, priorzptr);
       ++header->n_labels, ++priorzptr)
    {
      if (header->n_labels >= MAX_LABELS)
        {
          fprintf (stderr, "error: read_header: exceeded MAX_LABELS\n");
          return errno = ENOMEM, -1;
        }
    }

  header->priorz[0] = 0;
  for (unsigned int i = 1; i < header->n_labels; ++i)
    {
      header->priorz[0] += header->priorz[i];
    }
  header->priorz[0] = 1.0 - header->priorz[0];

  if (header->priorz[0] < 0.0 || header->priorz[0] > 1.0)
    {
      fprintf (stderr, "error: read_header: bad prior distribution\n");
      return errno = EINVAL, -1;
    }

  return 0;
}

static int
read_ratings (FILE*             fp,
              Data*             data)
{
  char buf[65536];
  unsigned int n;

  for (n = 0;
       n < data->n_ratings &&
       fgets (buf, sizeof (buf), fp) != NULL &&
       buf[strlen (buf) - 1] == '\n';
       ++n)
    {
      char* bufp = buf;

      if (read_nonnegative_integer (&bufp,
                                    &data->ratings[n].image,
                                    data->n_images,
                                    "imageId") ||
          read_nonnegative_integer (&bufp,
                                    &data->ratings[n].worker,
                                    data->n_workers,
                                    "workerId") || 
          read_nonnegative_integer (&bufp,
                                    &data->ratings[n].rating,
                                    data->n_labels,
                                    "labelId"))
        {
          fprintf (stderr, "error: read_data: bad data in record %u\n", n);
          return errno = EIO, -1;
        }

      ++data->image_ratings[data->ratings[n].image];
      ++data->worker_ratings[data->ratings[n].worker];
    }

  if (n < data->n_ratings)
    {
      fprintf (stderr, "error: read_data: unexpected EOF after record %u\n", n);
      return errno = EIO, -1;
    }

  return 0;
}

#define my_alloc(s) ({ size_t _s = (s);                         \
                       void* p = malloc (_s);                   \
                       if (p == NULL) {                         \
                         fprintf (stderr,                       \
                                  "malloc of %s failed\n",      \
                                  #s);                          \
                         return errno = ENOMEM, -1;             \
                       }                                        \
                       p; })

#define my_free(x) do { if ((x) != NULL) { free ((x)); } } while (0)

static int
read_lines (FILE*               fp,
            const Header*       header,
            Data*               data,
            unsigned int        rank)
{
  const float priorlogamean = 0;
  const float priorlogastddev = 1;
  const float priorlogbetamean = 1;
  const float priorlogbetastddev = 1;
  const float priorgammamean = 0;
  const float priorgammastddev = 1;
  const float priorcmean = 1.0 / rank;
  const float priorcstddev = 1;
  const float priordmean = 1;
  const float priordstddev = 1;

  data->loga = my_alloc (header->n_workers * rank * sizeof (*data->loga));
  for (unsigned int i = 0; i < header->n_workers * rank; ++i)
    {
      data->loga[i] = priorlogamean * (0.9 + 0.2 * drand48 ());
    }

  data->n_workers = header->n_workers;

  data->rank = rank;

  data->gamma = my_alloc (rank * sizeof (*data->gamma));
  for (unsigned int i = 0; i < rank; ++i)
    {
      data->gamma[i] = priorgammamean * (0.9 + 0.2 * drand48 ());
    }

  data->logbeta = my_alloc (header->n_images * sizeof (*data->logbeta));
  for (unsigned int i = 0; i < header->n_images; ++i)
    {
      data->logbeta[i] = priorlogbetamean;
    }

  data->n_images = header->n_images;

  data->c = my_alloc (header->n_labels * rank * sizeof (*data->c));
  for (unsigned int i = 0; i < header->n_labels * rank; ++i)
    {
      data->c[i] = priorcmean * (0.9 + 0.2 * drand48 ());
    }

  data->n_labels = header->n_labels;

  data->d = my_alloc (header->n_labels * rank * sizeof (*data->d));
  for (unsigned int i = 0; i < header->n_labels * rank; ++i)
    {
      data->d[i] = priordmean * (0.9 + 0.2 * drand48 ());
    }

  data->pz = 
    my_alloc (header->n_images * header->n_labels * sizeof (*data->pz));

  data->priorz = my_alloc (header->n_labels * sizeof (*data->priorz));
  memcpy (data->priorz,
          header->priorz,
          header->n_labels * sizeof (*data->priorz));

  data->clamp = my_alloc (header->n_images * sizeof (*data->clamp));
  for (unsigned int i = 0; i < header->n_images; ++i)
    {
      data->clamp[i] = -1;
    }

  data->ratings = my_alloc (header->n_ratings * sizeof (*data->ratings));
  data->n_ratings = header->n_ratings;

  data->priorloga = parameterized_normal_distribution (priorlogastddev);
  data->priorgamma = normal_distribution (priorgammamean,
                                          priorgammastddev);
  data->priorlogbeta = normal_distribution (priorlogbetamean,
                                            priorlogbetastddev);
  data->priorc = normal_distribution (priorcmean, priorcstddev);
  data->priord = normal_distribution (priordmean, priordstddev);
  data->image_ratings = 
    my_alloc (header->n_images * sizeof (*data->image_ratings));
  memset (data->image_ratings,
          0,
          header->n_images * sizeof (*data->image_ratings));
  data->worker_ratings = 
    my_alloc (header->n_workers * sizeof (*data->worker_ratings));
  memset (data->worker_ratings,
          0,
          header->n_workers * sizeof (*data->worker_ratings));

  return read_ratings (fp, data);
}

static int
print_header (FILE*             fp,
              const Data*       data)
{
  fprintf (fp, "%u %u %u", data->n_ratings, data->n_workers, data->n_images);

  for (unsigned int i = 1; i < data->n_labels; ++i)
    {
      fprintf (fp, " %g", data->priorz[i]);
    }

  fprintf (fp, "\n");

  return 0;
}

static int
print_lines (FILE*              fp,
             const Data*        data)
{
  for (unsigned int i = 0; i < data->n_ratings; ++i)
    {
      fprintf (fp, "%u %u %u\n", 
               data->ratings[i].image,
               data->ratings[i].worker,
               data->ratings[i].rating);
    }

  return 0;
}

int
read_data (FILE*        fp,
           Data*        data,
           unsigned int rank)
{
  Header header;

  memset (data, 0, sizeof (*data));

  return read_header (fp, &header) || read_lines (fp, &header, data, rank);
}

int
read_clamp (FILE*       fp,
            Data*       data)
{
  char buf[65536];
  unsigned int n;

  if (fgets (buf, sizeof (buf), fp) == NULL ||
      buf[strlen (buf) - 1] != '\n')
    {
      fprintf (stderr, "warning: no clamp data specified\n");
      return 0;
    }

  {
    char* bufp = buf;
    if (read_nonnegative_integer (&bufp,
                                  &data->n_clamp,
                                  1 + data->n_images,
                                  "numClamp"))
      {
        return errno = EIO, -1;
      }
  }

  for (n = 0;
       n < data->n_clamp &&
       fgets (buf, sizeof (buf), fp) != NULL &&
       buf[strlen (buf) - 1] == '\n';
       ++n)
    {
      char* bufp = buf;
      unsigned int image;
      unsigned int label;

      if (read_nonnegative_integer (&bufp,
                                    &image,
                                    data->n_images,
                                    "imageId") ||
          read_nonnegative_integer (&bufp, 
                                    &label,
                                    data->n_labels,
                                    "labelId"))
        {
          fprintf (stderr, "error: read_clamp: bad data in record %u\n", n);
          return errno = EIO, -1;
        }

      if (data->clamp[image] >= 0)
        {
          fprintf (stderr, "error: read_clamp: duplicate image %u in record %u\n",
                   image,
                   n);

          return errno = EIO, -1;
        }

      data->clamp[image] = label;
    }

  if (n < data->n_clamp)
    {
      fprintf (stderr, "error: read_data: unexpected EOF after record %u\n", n);
      return errno = EIO, -1;
    }

  return 0;
}

int
print_data (FILE*       fp,
            const Data* data)
{
  return print_header (fp, data) || print_lines (fp, data);
}

int
print_clamp (FILE*       fp,
             const Data* data)
{
  fprintf (fp, "%u\n", data->n_clamp);

  for (unsigned int i = 0; i < data->n_images; ++i)
    {
      if (data->clamp[i] >= 0)
        {
          fprintf (fp, "%u %u\n", i, data->clamp[i]);
        }
    }

  return 0;
}

#define my_free(x) do { if ((x) != NULL) { free ((x)); } } while (0)

void
data_destruct (Data* data)
{
  my_free (data->worker_ratings);
  my_free (data->image_ratings);
  my_free (data->ratings);
  my_free (data->clamp);
  my_free (data->priorz);
  my_free (data->pz);
  my_free (data->d);
  my_free (data->c);
  my_free (data->logbeta);
  my_free (data->gamma);
  my_free (data->loga);
}
